#!/bin/env python
"""A debugger that emulates the gdb4 behaviour to be used with SNiFF+"""
 
import os,sys
try:
	sys.path.append(reduce(os.path.join,(os.environ["SNIFF_DIR"],'lib','python','Sniff')))
except KeyError:
	pass

from pydb import Pydb
from bdb import BdbQuit
import tempfile
import marshal
import repr
import string

class Gdb(Pydb):
	def __init__(self):
		Pydb.__init__(self)
		self.file_base_names={}
		self.args=[]
		self.program=''
		self.prompt='(gdb) '
		self.i_am_child=0
		self.tempfile=tempfile.mktemp()
		self.quit=0
		self.print_args={'indent':2,'cutlen':40}
	def exit(self,status=0):
		if self.i_am_child:
			os._exit(status)
		else:
			# the parent removes the tempfile
			try:
				os.unlink(self.tempfile)
			except:
				pass
			sys.exit(status)
	def do_run(self,arg=None):
		"""run
		run an executable..."""
		if arg:
			self.args=self.split_args(arg)
		while self.fork_and_run(arg):
			print
			print "starting a new session"
			print
	def print_syntax_error(self,arg):
		print "******** SyntaxError ********"
		print "SyntaxError:", arg[0]
		file=arg[1][0]
		if not file:
			file=self.program
		print "\32\32%s:%d:" %(file,arg[1][1])
		self.cmdloop(1)

	def fork_and_run(self,arg=None):
		"""The real debugging session happens in a forked python process."""
		if self.i_am_child:
			# i can't run
			self.dump_status()
			self.reset()
			self.exit(0)
		pid = os.fork()
		if pid != 0:
			# Parent
			while 1:
				try:
					pid, sts = os.waitpid(pid, 0)
				except KeyboardInterrupt:
					print '\n[Interrupted]'
					# send the interrupt to the child process
					import signal
					os.kill(pid,signal.SIGINT)
					continue
				break
			self.load_status() # lst's load the childs status
			if self.quit:
				self.exit(0)
			return 1
		else:
			# Child
			self.i_am_child=1
			while 1:
				try:
					file=open(self.program)
				except:
					self.quit=1
					self.dump_status()
					print sys.exc_value[1]+":",self.program
					self.exit(1)
				prog=file.read()
				file.close()
				try:
					cprog=compile(prog,self.program,'exec')
				except SyntaxError,arg:
					self.print_syntax_error(arg)
				else:
					break
			# set the arguments correctly
			sys.argv=[self.program]+self.args
			try:
				self.runexec(cprog)
			finally:
				self.dump_status()
			self.exit(0)
	# The child process tells the parent
	# about its state using a tmp file.
	# When a breakpoint is set in the child,
	# the parent receives it and a new child will have 
	# the breakpoints set as well.
	def load_status(self):
		try:
			f=open(self.tempfile)
			self.breaks,self.args,self.quit,self.program,self.print_args=marshal.load(f)
			f.close()
		except IOError:
			pass
	def dump_status(self):
		f=open(self.tempfile,'w')
		marshal.dump((self.breaks,self.args,self.quit,self.program,self.print_args),f)
		f.close()
 
	def do_set(self,arg):
		"""set
		ARGUMENTS
			set arg {arg}*
			set program prognme
			set print [ arg=value {',' arg=value} ]
				Use arg=None to reset a value.
				Arg can be:
					indent=<int>|<string>
						indentation
					depth=<int>
						indentation when PrettyPrinting
					cutlen=<int>
						Cut strings (in the moddle '...') that are longer 
						than cutlen.
					duplicate=<bool>
						Print duplicate objects.
					show_id=<bool>
						Show id() of each object.
					private=<bool>
						Show private variables
				Defaults are:
					indent=2, cutlen=40, duplicate=0, 
					show_id=0, private=0
		"""
		args=self.split_args(arg)
		if args:
			what=args[0]
		else:
			what="?"
		args=args[1:]
		if what=='args':
			self.set_args(args)
		elif what=='program':
			self.set_program(args[0])
		elif what in ('print','pp'):
			self.set_print(args)
		else:
			print "OOPS: set ( 'args' | 'program' |  'print' ) {arg}"
		self.dump_status()
	def set_args(self,args):
		self.args=args
	allowed_print_args=('indent','depth','duplicate','show_id','private','cutlen')
	def set_print(self,args):
		if len(args)==0:
			print self.print_args
			return 
		a=string.join(args," ")
		def f(**kw):
			return kw
		try:
			kwds=eval("f(%s)" % a)
		except:
			print "Wrong print args syntax: arg=val {',' arg=val}",a
			return
		for k,v in kwds.items():
			if k in self.allowed_print_args:
				if v is None:
					try:
						del self.print_args[k]
					except:
						pass
				else:
					self.print_args[k]=v
 
		print self.print_args
	def do_cd(self,arg):
		"""cd directory
		Change the current working directory."""
		try:
			os.chdir(arg)
		except:
			pass
	def do_pwd(self,arg):
		"""pwd
		Show the current working directory."""
		print os.getcwd()
	def make_program_name(self,program):
		"""make a name in path from a fullname...."""
		# this might be a complete hack...

		basename=os.path.basename(program)
		fn=self.fullname(program)
		if fn and fn!=basename:
			program=fn
		return program

	def set_program(self,program):
		program=self.find_program(program)
		# set the sys.argv
		sys.argv=[program]
		# set the first entry of path to the path of the
		# main program
		p=os.path.split(program)[0]
		try:
			i=sys.path.index('')
			sys.path[i]=p
		except:
			sys.path.insert(0,p)

		self.program=self.make_program_name(program)
		print self.program
 
	def find_program(self,program):
		"""Finds an exeutable in the PATH"""
		if os.path.exists(program):
			return program
		env = os.environ
		head, tail = os.path.split(program)
		if env.has_key('PATH'):
			envpath = env['PATH']
			PATH = string.splitfields(envpath, os.pathsep)
			for dir in PATH:
				fullname = os.path.join(dir, program)
				if os.path.exists(fullname):
					return fullname
		return program

	def do_quit(self, arg):
		"""q(uit)	Quit from the debugger.
		The program being executed is aborted."""
		self.quit=1
		self.dump_status()
		self.exit(0)
	def do_print(self,argx):
		"""print expression
		Print the value of the expression.
		If the expresstion starts with *, the pretty print it."""
		def Print(x):print x
		arg=argx
		if arg and arg[0]=='*':
			try:
				from PrettyPrinter import PrettyPrinter
				try:
					Print=apply(PrettyPrinter,(),self.print_args).Print
				except:
					Print=PrettyPrinter().Print
			except:
				pass
			arg=arg[1:]
		self.curframe.f_globals['__privileged__'] = 1
		try:
			value = eval(arg, self.curframe.f_globals,
							self.curframe.f_locals)
		except:
			if arg=='this':
				return self.do_print(argx[:-4]+'self')
			if type(sys.exc_type) == type(''):
				exc_type_name = sys.exc_type
			else: exc_type_name = sys.exc_type.__name__
			print '***', exc_type_name + ':', `sys.exc_value`
			return
		Print(value)
	def user_exception(self, frame, (exc_type, exc_value, exc_traceback)):
		"""This function is called if an exception occurs.
		But only if we are to stop at or just below this level."""
		frame.f_locals['__exception__'] = exc_type, exc_value
		if type(exc_type) == type(''):
			exc_type_name = exc_type
		else: 
			exc_type_name = exc_type.__name__
		if exc_type==SystemExit:
			#self.exit(0)
			pass
		elif exc_type==SyntaxError:
			self.print_syntax_error(exc_value)
			return
		print "\n***Exception***"
		print exc_type_name + ':', repr.repr(exc_value)
		self.interaction(frame, exc_traceback)

	def runexec(self, expr, globals=None, locals=None):
		if globals is None:
			globals={
				'__doc__'      : None,
				'__name__'     : '__main__',
				'__builtins__' : __builtins__
				}
		if locals is None:
			locals = globals
		self.reset()
		sys.settrace(self.trace_dispatch)
		try:
			try:
				# eval in eval to be able to do continue in the
				# outer most frame.
				# Whithout this trick it always stops in the outmost
				# module in each line when you do 'continue'.
				eval('eval(expr, globals, locals)',vars())
				print "Program finished successfully"
			except BdbQuit:
				pass
			except:
				print "UNEXPECTED INTERNAL ERROR:"
				#print sys.exc_type + ':', repr.repr(sys.exc_value)
		finally:
			self.quitting = 1
			sys.settrace(None)
	# PROBLEM TO BE SOLVED:
	# dir/xx.py
	# cd dir
	# compile xx.py -> co_filename='xx.py'
	# cd ../otherdir
	# import xx.py -> co_filename='xx.py'
	# compile xx.py -> co_filename='dir/xx.py'
	def break_anywhere(self, frame):
		fn=frame.f_code.co_filename
		fbn=self.file_base_names
		if not fbn.has_key(fn):
			fbn[fn]=os.path.basename(fn)
		fn=fbn[fn]
		return self.breaks.has_key(fn)
	def break_here(self, frame):
		fn=frame.f_code.co_filename
		fbn=self.file_base_names
		if not fbn.has_key(fn):
			fbn[fn]=os.path.basename(fn)
		fn=fbn[fn]
		if not self.breaks.has_key(fn):
			return 0
		if not frame.f_lineno in \
				self.breaks[fn]:
			return 0
		return 1
 
def IsPython(file):
	import regex
	try:
		data=open(file).read(100)
	except IOError:
		return 1 # hack!
	for c in data:
		if ord(c) > 128:
			return 0
	return 1
 
def main():
	gdb=Gdb()
	progname=""
	# XXX cmdline parsing need rewrite
	argv=sys.argv[1:]
	# print argv
	while argv:
		arg=argv[0];del argv[0]
		if arg[0]=='-':
			if arg=='-cd':
				arg=argv[0];del argv[0]
				os.chdir(arg)
			elif arg=='-command':
				arg=argv[0];del argv[0]
		else:
			progname=arg
			break
	gdb.set_args(argv)
	if not progname:
		print "USAGE: %s [-cd dir] program [arg]..." %sys.argv[0]
		return
	if not IsPython(progname):
		#os.execvp('gdb',sys.argv[1:])
		import __builtin__
		args=string.join(map(__builtin__.repr,sys.argv[1:]),' ')
		sys.exit(os.system('gdb %s' %args))
	gdb.set_program(progname)
	gdb.do_run()
if __name__=='__main__':
	main()
 
